/*
 * xsuperlock
 *
 * Copyright (C) 2015 Alexander Andrejevic <theflash AT sdf DOT lonestar DOT org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <signal.h>
#include <time.h>
#include <poll.h>
#include <pwd.h>
#include <crypt.h>
#include <shadow.h>
#include <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#include <X11/extensions/xf86vmode.h>
#include "config.h"

#define POLL_TIMEOUT 200
#define BUFFER_SIZE 256

typedef struct screen_descriptor
{
    Window wnd;
    pid_t saver_pid;
    int number;
    Visual *visual;
    Colormap colormap;
    GC gc;
    unsigned int width;
    unsigned int height;
    XftDraw *xft_draw;
    Cursor blank_cursor;
    int ramp_size;
    unsigned short *red_ramp;
    unsigned short *green_ramp;
    unsigned short *blue_ramp;
} screen_descriptor_t;

Display *dpy;
screen_descriptor_t *screen_descriptors = NULL;
int default_screen, screen_count;
char password[BUFFER_SIZE];
Bool dialog_displayed = False;
unsigned int idle_counter = 0, incorrect_counter = 0;

char *screensaver_dir = DEFAULT_SCREENSAVER_DIR;
char *screensaver_name = NULL;
char *screensaver_opts = NULL;
char *correct_pwd_hash = NULL;
unsigned int incorrect_timeout = 3;
unsigned int idle_timeout = 30;
Bool fade_enabled = False;
unsigned int fade_timeout = 2000;

unsigned int dialog_width = 400;
unsigned int dialog_height = 300;
XColor dialog_bg_color = { .red = 0xEEEE, .green = 0xEEEE, .blue = 0xEEEE };

char *time_format = "%I:%M:%S %p";
char *time_label_font = "sans-18:bold";
int time_label_x = 0;
int time_label_y = 20;
XColor time_label_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };

char *date_format = "%Y/%m/%d";
char *date_label_font = "sans-12";
int date_label_x = 0;
int date_label_y = 45;
XColor date_label_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };

char *fullname_label = "%F";
char *fullname_label_font = "sans-18";
int fullname_label_x = 0;
int fullname_label_y = 100;
XColor fullname_label_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };

char *username_label = "%n on %H";
char *username_label_font = "sans-10";
int username_label_x = 0;
int username_label_y = 130;
XColor username_label_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };

char *password_label = "Password:";
char *password_label_font = "sans-10";
int password_label_x = 30;
int password_label_y = 200;
XColor password_label_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };

unsigned int pwd_dot_size = 12;
unsigned int pwd_field_width = 250;
unsigned int pwd_field_height = 20;
unsigned int pwd_field_border_width = 2;
int pwd_field_x = -30;
int pwd_field_y = 195;
XColor pwd_field_border_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };
XColor pwd_field_bg_color = { .red = 0xFFFF, .green = 0xFFFF, .blue = 0xFFFF };
XColor pwd_field_dot_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };

char *incorrect_label = "Incorrect password!";
char *incorrect_label_font = "sans-10:bold";
int incorrect_label_x = 0;
int incorrect_label_y = 225;
XColor incorrect_label_color = { .red = 0xFFFF, .green = 0x0000, .blue = 0x0000 };

unsigned int button_width = 95;
unsigned int button_height = 25;
unsigned int button_border_width = 2;
char *button_label_font = "sans-10";

char *unlock_button_label = "Unlock";
int unlock_button_x = 100;
int unlock_button_y = 260;
XColor unlock_button_bg_color = { .red = 0xFFFF, .green = 0xFFFF, .blue = 0xFFFF };
XColor unlock_button_border_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };
XColor unlock_button_label_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 }; 

char *cancel_button_label = "Cancel";
int cancel_button_x = 205;
int cancel_button_y = 260;
XColor cancel_button_bg_color = { .red = 0xFFFF, .green = 0xFFFF, .blue = 0xFFFF };
XColor cancel_button_border_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };
XColor cancel_button_label_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 }; 

size_t strfpasswd(char *buffer, size_t size, const char *format, const struct passwd *pw)
{
    size_t written = 0;
    char uid[BUFFER_SIZE];
    char gid[BUFFER_SIZE];
    char fullname[BUFFER_SIZE];
    char hostname[BUFFER_SIZE];

    if (size == 0) return 0;

    snprintf(uid, BUFFER_SIZE, "%lu", (unsigned long)pw->pw_uid);
    snprintf(gid, BUFFER_SIZE, "%lu", (unsigned long)pw->pw_gid);

    size_t name_length = strlen(pw->pw_gecos);
    char *last_comma = strchr(pw->pw_gecos, ',');
    if (last_comma) name_length = (size_t)(last_comma - pw->pw_gecos);
    if (name_length > BUFFER_SIZE - 1) name_length = BUFFER_SIZE - 1;

    memset(fullname, 0, sizeof(fullname));
    strncpy(fullname, pw->pw_gecos, name_length);

    memset(hostname, 0, sizeof(hostname));
    gethostname(hostname, BUFFER_SIZE - 1);

    while (*format && written < (size - 1))
    {
        if (*format == '%')
        {
            const char *to_append = NULL;

            switch (*++format)
            {
            case '%':
                to_append = "%";
                break;

            case 'n':
                to_append = pw->pw_name;
                break;

            case 'u':
                to_append = uid;
                break;

            case 'g':
                to_append = gid;
                break;

            case 'h':
                to_append = pw->pw_dir;
                break;

            case 'H':
                to_append = hostname;
                break;

            case 's':
                to_append = pw->pw_shell;
                break;

            case 'F':
                to_append = fullname;
                break;

            default:
                goto skip;
            }

            size_t amount = strlen(to_append);
            if (amount > (size - written - 1)) amount = size - written - 1;

            memcpy(buffer, to_append, amount);
            buffer += amount;
            written += amount;

skip:
            if (*format) format++;
        }
        else
        {
            *buffer++ = *format++;
            written++;
        }
    }

    *buffer = 0;
    return written;
}

void start_screensaver(screen_descriptor_t *screen)
{
    pid_t pid = fork();

    if (pid > 0)
    {
        screen->saver_pid = pid;
    }
    else if (pid == 0)
    {
        int i;
        char *token = NULL;
        char *cmdline = NULL;

        char hex_buffer[11];
        snprintf(hex_buffer, 11, "0x%08X", screen->wnd);

        char *path = (char*)alloca((strlen(screensaver_dir) + strlen(screensaver_name) + 2) * sizeof(char));
        strcpy(path, screensaver_dir);
        strcat(path, "/");
        strcat(path, screensaver_name);

        int argc = 3;
        if (screensaver_opts)
        {
            cmdline = (char*)alloca((strlen(screensaver_opts) + 1) * sizeof(char));
            strcpy(cmdline, screensaver_opts);
            
            for (token = strtok(cmdline, " "); token; token = strtok(NULL, " "))
            {
                if (strlen(token) > 0) argc++;
            }
        }

        char **argv = (char**)alloca((argc + 1) * sizeof(char*));
        argv[0] = screensaver_name;
        argv[1] = "-window-id";
        argv[2] = hex_buffer;
        argv[argc] = NULL;

        if (cmdline)
        {
            int i = 3;
            strcpy(cmdline, screensaver_opts);

            for (token = strtok(cmdline, " "); token && (i < argc); token = strtok(NULL, " "))
            {
                if (strlen(token) > 0) argv[i++] = token;
            }
        }

        execv(path, argv);

        perror("Couldn't exec screensaver process");
        exit(EXIT_FAILURE);
    }
    else
    {
        perror("Couldn't fork screensaver process");
    }
}

XRectangle get_absolute_rect(screen_descriptor_t *screen,
                             int x,
                             int y,
                             unsigned int width,
                             unsigned height)
{
    if (x < 0) x += dialog_width - width;
    else if (x == 0) x = (dialog_width - width) / 2;

    x += (screen->width - dialog_width) / 2;
    y += (screen->height - dialog_height) / 2;

    XRectangle result = { x, y, width, height };
    return result;
}

void paint_label(screen_descriptor_t *screen,
                 int x,
                 int y,
                 XColor *color,
                 const char *font_name,
                 const char *text)
{
    XGlyphInfo extents;
    XftFont *font = XftFontOpenName(dpy, screen->number, font_name);
    XftColor xft_color;

    XftTextExtents8(dpy, font, text, strlen(text), &extents);
    XRectangle rect = get_absolute_rect(screen, x, y, extents.width, extents.height);

    color->flags = DoRed | DoGreen | DoBlue;
    dialog_bg_color.flags = DoRed | DoGreen | DoBlue;

    XAllocColor(dpy, screen->colormap, color);
    XAllocColor(dpy, screen->colormap, &dialog_bg_color);

    xft_color.pixel = color->pixel;
    xft_color.color.red = color->red;
    xft_color.color.green = color->green;
    xft_color.color.blue = color->blue;
    xft_color.color.alpha = 0xFFFF;

    XSetForeground(dpy, screen->gc, dialog_bg_color.pixel);
    XFillRectangle(dpy, screen->wnd, screen->gc, rect.x - 1, rect.y - 1, rect.width + 2, rect.height + 2);
    XftDrawString8(screen->xft_draw, &xft_color, font, rect.x, rect.y + rect.height, text, strlen(text));

    unsigned long pixels[2] = { color->pixel, dialog_bg_color.pixel };
    XFreeColors(dpy, screen->colormap, pixels, 2, 0);

    XftFontClose(dpy, font);
}

void paint_button(screen_descriptor_t *screen,
                  int x,
                  int y,
                  XColor *border_color,
                  XColor *background_color,
                  XColor *text_color,
                  const char *text)
{
    XGlyphInfo extents;
    XftFont *font = XftFontOpenName(dpy, screen->number, button_label_font);
    XftColor xft_color;
    XRectangle rect = get_absolute_rect(screen, x, y, button_width, button_height);

    background_color->flags = DoRed | DoGreen | DoBlue;
    border_color->flags = DoRed | DoGreen | DoBlue;
    text_color->flags = DoRed | DoGreen | DoBlue;

    XAllocColor(dpy, screen->colormap, background_color);
    XAllocColor(dpy, screen->colormap, border_color);
    XAllocColor(dpy, screen->colormap, text_color);

    xft_color.pixel = text_color->pixel;
    xft_color.color.red = text_color->red;
    xft_color.color.green = text_color->green;
    xft_color.color.blue = text_color->blue;
    xft_color.color.alpha = 0xFFFF;

    XSetForeground(dpy, screen->gc, background_color->pixel);
    XFillRectangle(dpy, screen->wnd, screen->gc, rect.x, rect.y, rect.width, rect.height);

    XSetForeground(dpy, screen->gc, border_color->pixel);
    XSetLineAttributes(dpy, screen->gc, button_border_width, LineSolid, CapButt, JoinMiter);
    XDrawRectangle(dpy, screen->wnd, screen->gc, rect.x, rect.y, rect.width, rect.height);

    XftTextExtents8(dpy, font, text, strlen(text), &extents);
    XftDrawString8(screen->xft_draw,
                   &xft_color,
                   font,
                   rect.x + (rect.width - extents.width) / 2,
                   rect.y + (rect.height - extents.height) / 2 + extents.height,
                   text,
                   strlen(text));

    XftFontClose(dpy, font);

    unsigned long pixels[3] = { background_color->pixel, border_color->pixel, text_color->pixel };
    XFreeColors(dpy, screen->colormap, pixels, 3, 0);
}

void paint_password_field(screen_descriptor_t *screen, unsigned int num_dots)
{
    unsigned int i;
    XRectangle rect = get_absolute_rect(screen, pwd_field_x, pwd_field_y, pwd_field_width, pwd_field_height);

    pwd_field_bg_color.flags = DoRed | DoGreen | DoBlue;
    pwd_field_border_color.flags = DoRed | DoGreen | DoBlue;
    pwd_field_dot_color.flags = DoRed | DoGreen | DoBlue;
    XAllocColor(dpy, screen->colormap, &pwd_field_bg_color);
    XAllocColor(dpy, screen->colormap, &pwd_field_border_color);
    XAllocColor(dpy, screen->colormap, &pwd_field_dot_color);

    XSetForeground(dpy, screen->gc, pwd_field_bg_color.pixel);
    XFillRectangle(dpy, screen->wnd, screen->gc, rect.x, rect.y, rect.width, rect.height);

    XSetClipRectangles(dpy, screen->gc, 0, 0, &rect, 1, Unsorted);
    XSetForeground(dpy, screen->gc, pwd_field_dot_color.pixel);

    for (i = 0; i < num_dots; i++)
    {
        int dot_x = rect.x + (pwd_dot_size + 1) * i + pwd_field_border_width + 1;
        int dot_y = rect.y + (rect.height - pwd_dot_size) / 2;

        XFillArc(dpy, screen->wnd, screen->gc, dot_x, dot_y, pwd_dot_size, pwd_dot_size, 0, 360 * 64);
    }

    XSetClipMask(dpy, screen->gc, None);

    XSetForeground(dpy, screen->gc, pwd_field_border_color.pixel);
    XSetLineAttributes(dpy, screen->gc, pwd_field_border_width, LineSolid, CapButt, JoinMiter);
    XDrawRectangle(dpy, screen->wnd, screen->gc, rect.x, rect.y, rect.width, rect.height);

    unsigned long pixels[3] =
    {
        pwd_field_bg_color.pixel,
        pwd_field_border_color.pixel,
        pwd_field_dot_color.pixel
    };

    XFreeColors(dpy, screen->colormap, pixels, 3, 0);
}

void paint_clock(screen_descriptor_t *screen, time_t timestamp)
{
    char buffer[BUFFER_SIZE];
    struct tm *time_data = localtime(&timestamp);

    strftime(buffer, BUFFER_SIZE, time_format, time_data);
    paint_label(screen, time_label_x, time_label_y, &time_label_color, time_label_font, buffer);

    strftime(buffer, BUFFER_SIZE, date_format, time_data);
    paint_label(screen, date_label_x, date_label_y, &date_label_color, date_label_font, buffer);
}

void paint_dialog(screen_descriptor_t *screen)
{
    char buffer[BUFFER_SIZE];
    unsigned int dialog_x = (screen->width - dialog_width) / 2;
    unsigned int dialog_y = (screen->height - dialog_height) / 2;
    time_t timestamp;
    struct passwd *user_info = getpwuid(getuid());

    dialog_bg_color.flags = DoRed | DoGreen | DoBlue;
    XAllocColor(dpy, screen->colormap, &dialog_bg_color);
    XSetForeground(dpy, screen->gc, dialog_bg_color.pixel);
    XFillRectangle(dpy, screen->wnd, screen->gc, dialog_x, dialog_y, dialog_width, dialog_height);
    XFreeColors(dpy, screen->colormap, &dialog_bg_color.pixel, 1, 0);

    time(&timestamp);
    paint_clock(screen, timestamp);

    strfpasswd(buffer, BUFFER_SIZE, fullname_label, user_info);
    paint_label(screen,
                fullname_label_x,
                fullname_label_y,
                &fullname_label_color,
                fullname_label_font,
                buffer);

    strfpasswd(buffer, BUFFER_SIZE, username_label, user_info);
    paint_label(screen,
                username_label_x,
                username_label_y,
                &username_label_color,
                username_label_font,
                buffer);

    paint_label(screen,
                password_label_x,
                password_label_y,
                &password_label_color,
                password_label_font,
                password_label);

    paint_password_field(screen, strlen(password));

    paint_button(screen,
                 unlock_button_x,
                 unlock_button_y,
                 &unlock_button_border_color,
                 &unlock_button_bg_color,
                 &unlock_button_label_color,
                 unlock_button_label);

    paint_button(screen,
                 cancel_button_x,
                 cancel_button_y,
                 &cancel_button_border_color,
                 &cancel_button_bg_color,
                 &cancel_button_label_color,
                 cancel_button_label);

    if (incorrect_counter > 0)
    {
        paint_label(screen,
                    incorrect_label_x,
                    incorrect_label_y,
                    &incorrect_label_color,
                    incorrect_label_font,
                    incorrect_label);
    }
}

void set_brightness(screen_descriptor_t *screen, unsigned int percentage)
{
    int i;

    unsigned short *red_ramp = alloca(screen->ramp_size * sizeof(unsigned short));
    unsigned short *green_ramp = alloca(screen->ramp_size * sizeof(unsigned short));
    unsigned short *blue_ramp = alloca(screen->ramp_size * sizeof(unsigned short));

    for (i = 0; i < screen->ramp_size; i++)
    {
        red_ramp[i] = (unsigned short)(((unsigned int)screen->red_ramp[i] * percentage) / 100);
        green_ramp[i] = (unsigned short)(((unsigned int)screen->green_ramp[i] * percentage) / 100);
        blue_ramp[i] = (unsigned short)(((unsigned int)screen->blue_ramp[i] * percentage) / 100);
    }

    XF86VidModeSetGammaRamp(dpy, screen->number, screen->ramp_size, red_ramp, green_ramp, blue_ramp);
}

Bool create_windows(void)
{
    int i;

    for (i = 0; i < screen_count; i++)
    {
        Window root_wnd = RootWindow(dpy, i);
        screen_descriptor_t *screen = &screen_descriptors[i];
        memset(screen, 0, sizeof(screen_descriptor_t));

        if (XGrabKeyboard(dpy, root_wnd, False, GrabModeAsync, GrabModeAsync, CurrentTime) != GrabSuccess)
        {
            fprintf(stderr, "Could not grab the keyboard on screen %d\n", i);
            return False;
        }

        if (XGrabPointer(dpy,
                         root_wnd,
                         False,
                         PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
                         GrabModeAsync,
                         GrabModeAsync,
                         None,
                         None,
                         CurrentTime)
            != GrabSuccess)
        {
            fprintf(stderr, "Could not grab the pointer on screen %d\n", i);
            return False;
        }

        XSetWindowAttributes wa =
        {
            .override_redirect = 1,
            .background_pixel = BlackPixel(dpy, i),
            .event_mask = ExposureMask
        };

        XGCValues gcv =
        {
            .foreground = WhitePixel(dpy, i),
            .background = BlackPixel(dpy, i),
            .line_width = 1
        };

        screen->number = i;
        screen->saver_pid = 0;
        screen->width = DisplayWidth(dpy, i);
        screen->height = DisplayHeight(dpy, i);
        screen->visual = DefaultVisual(dpy, i);
        screen->colormap = DefaultColormap(dpy, i);
        screen->wnd = XCreateWindow(dpy,
                                    root_wnd,
                                    0,
                                    0,
                                    screen->width,
                                    screen->height,
                                    0,
                                    DefaultDepth(dpy, i),
                                    InputOutput,
                                    screen->visual,
                                    CWOverrideRedirect | CWBackPixel | CWEventMask,
                                    &wa);
        screen->gc = XCreateGC(dpy, screen->wnd, GCForeground | GCBackground | GCLineWidth, &gcv);
        screen->xft_draw = XftDrawCreate(dpy, screen->wnd, screen->visual, screen->colormap);

        Pixmap cursor_pixmap = XCreatePixmap(dpy, root_wnd, 1, 1, 1);
        XColor cursor_color = { .red = 0x0000, .green = 0x0000, .blue = 0x0000 };

        screen->blank_cursor = XCreatePixmapCursor(dpy,
                                                   cursor_pixmap,
                                                   cursor_pixmap,
                                                   &cursor_color,
                                                   &cursor_color,
                                                   0,
                                                   0);
        XDefineCursor(dpy, screen->wnd, screen->blank_cursor);
        XFreePixmap(dpy, cursor_pixmap);

        if (fade_enabled)
        {
            if (!XF86VidModeGetGammaRampSize(dpy, i, &screen->ramp_size))
            {
                fprintf(stderr, "Could not find the gamma ramp size. Fading will not work.");
                fade_enabled = False;
            }
        }

        if (fade_enabled)
        {
            screen->red_ramp = (unsigned short*)malloc(screen->ramp_size * sizeof(unsigned short));
            screen->green_ramp = (unsigned short*)malloc(screen->ramp_size * sizeof(unsigned short));
            screen->blue_ramp = (unsigned short*)malloc(screen->ramp_size * sizeof(unsigned short));

            if (!screen->red_ramp || !screen->green_ramp || !screen->blue_ramp)
            {
                fprintf(stderr, "Could not allocate memory for the gamma ramp. Fading will not work.");

                if (screen->red_ramp) free(screen->red_ramp);
                if (screen->green_ramp) free(screen->green_ramp);
                if (screen->blue_ramp) free(screen->blue_ramp);

                screen->red_ramp = screen->green_ramp = screen->blue_ramp = NULL;
                fade_enabled = False;
            }
        }

        if (fade_enabled)
        {
            if (!XF86VidModeGetGammaRamp(dpy,
                                         i,
                                         screen->ramp_size,
                                         screen->red_ramp,
                                         screen->green_ramp,
                                         screen->blue_ramp))
            {
                fprintf(stderr, "Could not extract the gamma ramp. Fading will not work.");

                if (screen->red_ramp) free(screen->red_ramp);
                if (screen->green_ramp) free(screen->green_ramp);
                if (screen->blue_ramp) free(screen->blue_ramp);

                screen->red_ramp = screen->green_ramp = screen->blue_ramp = NULL;
                fade_enabled = False;
            }
        }
    }

    return True;
}

void destroy_windows(void)
{
    int i;

    for (i = 0; i < screen_count; i++)
    {
        screen_descriptor_t *screen = &screen_descriptors[i];

        if (screen->saver_pid)
        {
            kill(screen->saver_pid, SIGCONT);
            kill(screen->saver_pid, SIGTERM);
            screen->saver_pid = 0;
        }

        if (fade_enabled) set_brightness(screen, 100);
        if (screen->red_ramp) free(screen->red_ramp);
        if (screen->green_ramp) free(screen->green_ramp);
        if (screen->blue_ramp) free(screen->blue_ramp);

        if (screen->blank_cursor) XFreeCursor(dpy, screen->blank_cursor);
        if (screen->xft_draw) XftDrawDestroy(screen->xft_draw);
        if (screen->gc) XFreeGC(dpy, screen->gc);

        if (screen->wnd)
        {
            XUnmapWindow(dpy, screen->wnd);
            XDestroyWindow(dpy, screen->wnd);
        }
    }
}

Bool show_dialog(screen_descriptor_t *screen)
{
    if (dialog_displayed) return True;

    if (screen->saver_pid)
    {
        kill(screen->saver_pid, SIGSTOP);

        Bool equal = False;
        XImage *prev_image = XGetImage(dpy, screen->wnd, 0, 0, screen->width, screen->height, AllPlanes, XYPixmap);

        while (!equal)
        {
            sched_yield();

            XImage *image = XGetImage(dpy, screen->wnd, 0, 0, screen->width, screen->height, AllPlanes, XYPixmap);
            if (!image) break;

            if (memcmp(image->data, prev_image->data, image->height * image->bytes_per_line) == 0)
            {
                equal = True;
            }

            XFree(prev_image);
            prev_image = image;
        }

        if (prev_image) XFree(prev_image);
    }

    dialog_displayed = True;
    idle_counter = 0;
    XUndefineCursor(dpy, screen->wnd);
    paint_dialog(screen);

    return False;
}

void hide_dialog(screen_descriptor_t *screen)
{
    if (dialog_displayed)
    {
        XDefineCursor(dpy, screen->wnd, screen->blank_cursor);

        dialog_displayed = False;
        memset(password, 0, sizeof(password));
        XClearWindow(dpy, screen->wnd);

        if (screen->saver_pid) kill(screen->saver_pid, SIGCONT);
    }
}

Bool check_password(void)
{
    if (strcmp(crypt(password, correct_pwd_hash), correct_pwd_hash) == 0) return True;

    incorrect_counter = incorrect_timeout;
    paint_label(&screen_descriptors[default_screen],
                incorrect_label_x,
                incorrect_label_y,
                &incorrect_label_color,
                incorrect_label_font,
                incorrect_label);

    return False;
}

void process_events(void)
{
    int i;
    XEvent ev;
    struct pollfd poll_descriptor;
    screen_descriptor_t *screen = &screen_descriptors[default_screen];
    KeySym keysym;
    char buffer[BUFFER_SIZE];
    XComposeStatus compose_status;
    time_t current_time, last_update;

    time(&last_update);
    poll_descriptor.fd = ConnectionNumber(dpy);
    poll_descriptor.events = POLLIN;

    while (True)
    {
        while (XPending(dpy))
        {
            XNextEvent(dpy, &ev);

            switch (ev.type)
            {
            case Expose:
                if (dialog_displayed) paint_dialog(screen);
                break;

            case MotionNotify:
                show_dialog(screen);
                break;

            case ButtonPress:
                if (incorrect_counter) break;
                if (!show_dialog(screen)) break;

                XRectangle u = get_absolute_rect(screen, unlock_button_x, unlock_button_y, button_width, button_height);
                XRectangle c = get_absolute_rect(screen, cancel_button_x, cancel_button_y, button_width, button_height);

                if ((ev.xbutton.x_root >= u.x) && (ev.xbutton.x_root <= (u.x + u.width))
                    && (ev.xbutton.y_root >= u.y) && (ev.xbutton.y_root <= (u.y + u.height)))
                {   
                    if (check_password()) return;
                }
                else if ((ev.xbutton.x_root >= c.x) && (ev.xbutton.x_root <= (c.x + c.width))
                    && (ev.xbutton.y_root >= c.y) && (ev.xbutton.y_root <= (c.y + c.height)))
                {   
                    hide_dialog(screen);
                }

                break;

            case KeyPress:
                if (incorrect_counter) break;
                Bool was_displayed = show_dialog(screen);

                idle_counter = 0;
                memset(buffer, 0, sizeof(buffer));
                XLookupString(&ev.xkey, buffer, sizeof(buffer) - 1, &keysym, &compose_status);

                if (keysym == XK_Escape)
                {
                    if (was_displayed) hide_dialog(screen);
                }
                else if (keysym == XK_Return || keysym == XK_KP_Enter || keysym == XK_Linefeed)
                {
                    if (was_displayed && check_password()) return;
                }
                else if (keysym == XK_BackSpace)
                {
                    size_t length = strlen(password);
                    if (length > 0) password[length - 1] = 0;
                    paint_password_field(screen, strlen(password)); 
                }
                else if (keysym < XK_Shift_L || keysym > XK_Hyper_R)
                {
                    strncat(password, buffer, BUFFER_SIZE - strlen(password) - 1);
                    paint_password_field(screen, strlen(password)); 
                }

                break;
            }
        }

        if (dialog_displayed)
        {
            time(&current_time);

            if (current_time != last_update)
            {
                paint_clock(screen, current_time);
                last_update = current_time;

                if (incorrect_counter)
                {
                    incorrect_counter--;
                    if (incorrect_counter == 0)
                    {
                        memset(password, 0, sizeof(password));
                        paint_dialog(screen);
                    }
                }

                idle_counter++;
                if (idle_counter == idle_timeout) hide_dialog(screen);
            }
        }

        poll(&poll_descriptor, 1, POLL_TIMEOUT);
    }
}

Bool fade(void)
{
    int i;
    XEvent ev;
    struct pollfd poll_descriptor;
    long fade_counter = (long)fade_timeout;
    struct timespec current_time, last_update, delta;

    if (!fade_enabled) return True;

    clock_gettime(CLOCK_MONOTONIC, &last_update);
    poll_descriptor.fd = ConnectionNumber(dpy);
    poll_descriptor.events = POLLIN;

    while (fade_counter)
    {
        while (XPending(dpy))
        {
            XNextEvent(dpy, &ev);

            switch (ev.type)
            {
            case MotionNotify:
            case ButtonPress:
            case KeyPress:
                return False;
            }
        }

        clock_gettime(CLOCK_MONOTONIC, &current_time);
        delta.tv_sec = current_time.tv_sec - last_update.tv_sec;
        delta.tv_nsec = current_time.tv_nsec - last_update.tv_nsec;

        if (current_time.tv_nsec < last_update.tv_nsec)
        {
            delta.tv_sec--;
            delta.tv_nsec += 1000000000L;
        }

        last_update = current_time;

        fade_counter -= (long)delta.tv_sec * 1000L + (delta.tv_nsec / 1000000L);
        if (fade_counter < 0) fade_counter = 0;

        for (i = 0; i < screen_count; i++)
        {
            set_brightness(&screen_descriptors[i], (fade_counter * 100) / fade_timeout);
        }

        poll(&poll_descriptor, 1, 1);
    }

    return True;
}

void load_string_setting(char *string, char **value)
{
    if (string) *value = string;
}

void load_integer_setting(char *string, int *value)
{
    int num;
    if (string && scanf(string, "%d", &num) == 1) *value = num;
}

void load_color_setting(char *string, XColor *color)
{
    unsigned short red, green, blue;

    if (string && sscanf(string, "%04hX/%04hX/%04hX", &red, &green, &blue) == 3)
    {
        color->red = red;
        color->green = green;
        color->blue = blue;
    }
}

void load_settings(void)
{
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "screensaverName"), &screensaver_name);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "screensaverDir"), &screensaver_dir);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "screensaverOpts"), &screensaver_opts);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "password"), &correct_pwd_hash);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "errorTimeout"), &incorrect_timeout);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "idleTimeout"), &idle_timeout);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "fadeTimeout"), &fade_timeout);

    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "dialogWidth"), &dialog_width);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "dialogHeight"), &dialog_height);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "dialogBgColor"), &dialog_bg_color);

    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "timeFormat"), &time_format);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "timeLabelFont"), &time_label_font);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "timeLabelX"), &time_label_x);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "timeLabelY"), &time_label_y);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "timeLabelColor"), &time_label_color);

    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "dateFormat"), &date_format);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "dateLabelFont"), &date_label_font);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "dateLabelX"), &date_label_x);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "dateLabelY"), &date_label_y);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "dateLabelColor"), &date_label_color);

    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "fullnameLabel"), &fullname_label);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "fullnameLabelFont"), &fullname_label_font);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "fullnameLabelX"), &fullname_label_x);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "fullnameLabelY"), &fullname_label_y);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "fullnameLabelColor"), &fullname_label_color);

    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "usernameLabel"), &username_label);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "usernameLabelFont"), &username_label_font);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "usernameLabelX"), &username_label_x);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "usernameLabelY"), &username_label_y);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "usernameLabelColor"), &username_label_color);

    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordLabel"), &password_label);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordLabelFont"), &password_label_font);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordLabelX"), &password_label_x);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordLabelY"), &password_label_y);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordLabelColor"), &password_label_color);

    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordDotSize"), &pwd_dot_size);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordFieldWidth"), &pwd_field_width);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordFieldHeight"), &pwd_field_height);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordFieldBorderWidth"), &pwd_field_border_width);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordFieldX"), &pwd_field_x);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordFieldY"), &pwd_field_y);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordFieldBorderColor"), &pwd_field_border_color);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordFieldBgColor"), &pwd_field_bg_color);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "passwordFieldDotColor"), &pwd_field_dot_color);

    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "incorrectLabel"), &incorrect_label);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "incorrectLabelFont"), &incorrect_label_font);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "incorrectLabelX"), &incorrect_label_x);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "incorrectLabelY"), &incorrect_label_y);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "incorrectLabelColor"), &incorrect_label_color);

    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "buttonWidth"), &button_width);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "buttonHeight"), &button_height);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "buttonBorderWidth"), &button_border_width);
    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "buttonLabelFont"), &button_label_font);

    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "unlockButtonLabel"), &button_label_font);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "unlockButtonX"), &unlock_button_x);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "unlockButtonY"), &unlock_button_y);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "unlockButtonBgColor"), &unlock_button_bg_color);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "unlockButtonBorderColor"), &unlock_button_border_color);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "unlockButtonLabelColor"), &unlock_button_label_color);

    load_string_setting(XGetDefault(dpy, PACKAGE_NAME, "cancelButtonLabel"), &button_label_font);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "cancelButtonX"), &cancel_button_x);
    load_integer_setting(XGetDefault(dpy, PACKAGE_NAME, "cancelButtonY"), &cancel_button_y);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "cancelButtonBgColor"), &cancel_button_bg_color);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "cancelButtonBorderColor"), &cancel_button_border_color);
    load_color_setting(XGetDefault(dpy, PACKAGE_NAME, "cancelButtonLabelColor"), &cancel_button_label_color);
}

void signal_handler(int signum)
{
    if (signum != SIGCHLD || screen_descriptors == NULL) return;

    pid_t pid = waitpid(-1, NULL, WNOHANG);
    if (pid == 0) return;

    int i;
    for (i = 0; i < screen_count; i++)
    {
        screen_descriptor_t *screen = &screen_descriptors[i];
        if (screen->saver_pid == pid) screen->saver_pid = 0;
    }
}

struct option options[] =
{
    { "display",          required_argument, NULL, 'd' },
    { "screensaver",      required_argument, NULL, 's' },
    { "screensaver-dir",  required_argument, NULL, 0   },
    { "screensaver-opts", required_argument, NULL, 'o' },
    { "fade",             no_argument,       NULL, 'f' },
    { "help",             no_argument,       NULL, 'h' },
    { "version",          no_argument,       NULL, 'V' },
    { NULL,               0,                 NULL, 0   }
};

int main(int argc, char *argv[])
{
    int opt, index;
    char *display_name = NULL;
    struct sigaction act;
    char *scrnsvr_dir = NULL;
    char *scrnsvr_name = NULL;
    char *scrnsvr_opts = NULL;

    act.sa_handler = signal_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_NOCLDSTOP;

    if (sigaction(SIGCHLD, &act, NULL))
    {
        perror("Couldn't set signal handler");
        return EXIT_FAILURE;
    }

    while ((opt = getopt_long(argc, argv, "d:s:o:fhV", options, &index)) != -1)
    {
        switch (opt)
        {
        case 0:
            if (strcmp(options[index].name, "screensaver-dir") == 0) scrnsvr_dir = optarg;
            break;

        case 'd':
            display_name = optarg;
            break;

        case 's':
            scrnsvr_name = optarg;
            break;

        case 'o':
            scrnsvr_opts = optarg;
            break;

        case 'f':
            fade_enabled = True;
            break;

        case 'h':
        case 'V':
            puts(PACKAGE_STRING " - A screen locker.\n"
                 "Copyright (C) 2015 Alexander Andrejevic <theflash AT sdf DOT lonestar DOT org>\n"
                 "License AGPLv3+: GNU AGPL version 3 or later <http://www.gnu.org/licenses/agpl.html>\n"
                 "This is free software: you are free to change and redistribute it.\n"
                 "There is NO WARRANTY, to the extent permitted by law.");
            if (opt == 'V') return EXIT_SUCCESS;

            puts("\nUsage: xsuperlock [options]\n\n"
                 "Options:\n"
                 "\t-d, --display\t\tThe X server display to use.\n"
                 "\t-s, --screensaver\tThe name of the screensaver executable to run.\n"
                 "\t--screensaver-dir\tThe directory that contains screensavers\n"
                 "\t\t\t\t(" DEFAULT_SCREENSAVER_DIR " by default)\n"
                 "\t-o, --screensaver-opts\tParameters to pass to the screensaver. Remember\n"
                 "\t\t\t\tto enclose them in quotes.\n"
                 "\t-f, --fade\t\tFade before locking.\n"
                 "\t-h, --help\t\tPrint this usage screen.\n"
                 "\t-V, --version\t\tPrint version information.\n");

            return EXIT_SUCCESS;

        default:
            break;
        }
    }

    dpy = XOpenDisplay(display_name);
    if (dpy == NULL)
    {
        fputs("Could not open display.\n", stderr);
        return EXIT_FAILURE;
    }

    load_settings();

    if (scrnsvr_name) screensaver_name = scrnsvr_name;
    if (scrnsvr_dir) screensaver_dir = scrnsvr_dir;
    if (scrnsvr_opts) screensaver_opts = scrnsvr_opts;

    if (correct_pwd_hash == NULL)
    {
        struct passwd *pw = getpwuid(getuid());
        struct spwd *shadow = getspnam(pw->pw_name);

        if (!shadow || !shadow->sp_pwdp)
        {
            fputs("Could not read the shadow file and no password specified.\n", stderr);

            XCloseDisplay(dpy);
            return EXIT_FAILURE;
        }

        correct_pwd_hash = shadow->sp_pwdp;
    }

    seteuid(getuid());
    setegid(getgid());

    memset(password, 0, sizeof(password));
    default_screen = DefaultScreen(dpy);
    screen_count = ScreenCount(dpy);
    screen_descriptors = (screen_descriptor_t*)malloc(sizeof(screen_descriptor_t) * screen_count);

    if (!create_windows()) goto done;
    if (!fade()) goto done;

    for (index = 0; index < screen_count; index++)
    {
        screen_descriptor_t *screen = &screen_descriptors[index];

        XMapRaised(dpy, screen->wnd);
        if (screensaver_name) start_screensaver(screen);
        if (fade_enabled) set_brightness(screen, 100);
    }

    process_events();

done:
    destroy_windows();

    free(screen_descriptors);
    screen_descriptors = NULL;

    XCloseDisplay(dpy);
    return EXIT_SUCCESS;
}
